package org.sqldroid;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;

import android.util.Log;

public class SqldroidConnection implements Connection {
	private org.sqldroid.SQLiteDatabase sqlitedb;
	private boolean autoCommit = true;

	public SqldroidConnection(final String url, final Properties info) throws SQLException {
		Log.i("Sqlandroid", "new sqlite jdbc from url '" + url + "', " + "'" + info + "'");

		// Make a filename from url
		final String dbQname = url.substring(SQLDroidDriver.sqldroidPrefix.length());
		Log.i("Sqlandroid", "opening database " + dbQname);

		final long timeout = 30000;
		final long start = System.currentTimeMillis();
		android.database.sqlite.SQLiteDatabase sqlitedb_native = null;
		while (System.currentTimeMillis() - start < timeout) {
			try {
				sqlitedb_native = android.database.sqlite.SQLiteDatabase.openDatabase(dbQname, null,
						android.database.sqlite.SQLiteDatabase.CREATE_IF_NECESSARY
								| android.database.sqlite.SQLiteDatabase.OPEN_READWRITE);
				break;
			} catch (final Exception e) {
				System.out.println("Exception opening SQLiteDatabase: " + e.getClass() + " " + e.getMessage());
				try {
					Thread.sleep(1000);
				} catch (final InterruptedException e1) {
					Thread.currentThread().interrupt();
				}
			}
		}
		if (sqlitedb_native == null) {
			throw new SQLException("Timeout opening database");
		}

		Log.i("Sqldroid", "Adding retry with timeout");
		sqlitedb = addRetryWithTimeout(sqlitedb_native);
	}

	/**
	 * Adds trace logging to the given object using a proxy decorator.
	 */
	public static SQLiteDatabase addRetryWithTimeout(final android.database.sqlite.SQLiteDatabase object) {
		final InvocationHandler handler = new InvocationHandler() {

			@Override
			public Object invoke(final Object proxy, final Method method, final Object[] args) throws SQLException {
				// System.out.println("With retry: " + method + "(" +
				// args.length + ")");
				final long timeout = 30000;
				final long start = System.currentTimeMillis();
				Method target_method;
				try {
					target_method = object.getClass().getMethod(method.getName(), method.getParameterTypes());
				} catch (final NoSuchMethodException e1) {
					throw new SQLException(e1);
				}
				while (System.currentTimeMillis() - start < timeout) {
					try {
						return target_method.invoke(object, args);
					} catch (final IllegalAccessException e) {
						throw new SQLException(e);
					} catch (final InvocationTargetException e) {
						throw new SQLException("Timeout writing to SQLiteDatabase", e.getCause());
					}
				}
				return null;
			}
		};

		return (SQLiteDatabase) Proxy.newProxyInstance(SqldroidConnection.class.getClassLoader(),
				new Class[] { SQLiteDatabase.class }, handler);
	}

	public SQLiteDatabase getDb() {
		return sqlitedb;
	}

	@Override
	public void clearWarnings() throws SQLException {
	}

	@Override
	public void close() throws SQLException {
		if (sqlitedb != null) {
			sqlitedb.close();
		}

		sqlitedb = null;
	}

	@Override
	public void commit() throws SQLException {
		if (autoCommit) {
			throw new SQLException("database in auto-commit mode");
		}
		sqlitedb.setTransactionSuccessful();
		sqlitedb.endTransaction();
		sqlitedb.beginTransaction();
	}

	@Override
	public Statement createStatement() throws SQLException {
		return new SqldroidStatement(this);
	}

	@Override
	public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public Statement createStatement(final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability)
			throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public boolean getAutoCommit() throws SQLException {
		return autoCommit;
	}

	@Override
	public String getCatalog() throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public int getHoldability() throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return 0;
	}

	@Override
	public DatabaseMetaData getMetaData() throws SQLException {

		return new SqldroidDatabaseMetaData(this);
	}

	@Override
	public int getTransactionIsolation() throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return 0;
	}

	@Override
	public Map<String, Class<?>> getTypeMap() throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public SQLWarning getWarnings() throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public boolean isClosed() throws SQLException {
		return sqlitedb == null || !sqlitedb.isOpen();
	}

	@Override
	public boolean isReadOnly() throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return false;
	}

	@Override
	public String nativeSQL(final String sql) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());
		sqlitedb.execSQL(sql);
		return "Sqldroid: no return info available from sqlite";
	}

	@Override
	public CallableStatement prepareCall(final String sql) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
			final int resultSetHoldability) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public PreparedStatement prepareStatement(final String sql) throws SQLException {
		return new SqldroidPreparedStatement(sql, this);
	}

	@Override
	public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
			throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
			final int resultSetHoldability) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

	}

	@Override
	public void rollback() throws SQLException {
		if (autoCommit) {
			throw new SQLException("database in auto-commit mode");
		}
		sqlitedb.endTransaction();
		sqlitedb.beginTransaction();
	}

	@Override
	public void rollback(final Savepoint savepoint) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());
	}

	@Override
	public void setAutoCommit(final boolean autoCommit) throws SQLException {
		if (this.autoCommit == autoCommit) {
			return;
		}
		this.autoCommit = autoCommit;
		if (autoCommit) {
			sqlitedb.setTransactionSuccessful();
			sqlitedb.endTransaction();
		} else {
			sqlitedb.beginTransaction();
		}
	}

	@Override
	public void setCatalog(final String catalog) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

	}

	@Override
	public void setHoldability(final int holdability) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

	}

	@Override
	public void setReadOnly(final boolean readOnly) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

	}

	@Override
	public Savepoint setSavepoint() throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public Savepoint setSavepoint(final String name) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

		return null;
	}

	@Override
	public void setTransactionIsolation(final int level) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

	}

	@Override
	public void setTypeMap(final Map<String, Class<?>> arg0) throws SQLException {
		System.err.println(" ********************* not implemented @ " + DebugPrinter.getFileName() + " line "
				+ DebugPrinter.getLineNumber());

	}

	@Override
	protected void finalize() throws Throwable {
		Log.i("Sqldroid", " --- Finalize Sqldroid, closing db.");
		if (sqlitedb != null) {
			sqlitedb.close();
		}

		sqlitedb = null;
		super.finalize();
	}

	@Override
	public boolean isWrapperFor(final Class<?> arg0) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public <T> T unwrap(final Class<T> arg0) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Blob createBlob() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Clob createClob() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public NClob createNClob() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public SQLXML createSQLXML() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Properties getClientInfo() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String getClientInfo(final String name) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isValid(final int timeout) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void setClientInfo(final Properties properties) throws SQLClientInfoException {
		// TODO Auto-generated method stub

	}

	@Override
	public void setClientInfo(final String name, final String value) throws SQLClientInfoException {
		// TODO Auto-generated method stub

	}
}
